﻿using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using AndreySmirnov.BLToolkitUtils.VSPackage.Model;
using AndreySmirnov.BLToolkitUtils.VSPackage.Model.SchemaProvider;
using BLToolkit.Data;
using BLToolkit.Data.Linq;

namespace AndreySmirnov.BLToolkitUtils.VSPackage.CodeGeneration
{
	public class DbContextCodeGeneration
	{
		public const string DB_CONTEXT_SUFFIX = "DbContext";

		public CodeCompileUnit GenerateDbContext(string rootNamespace, 
            string contextPrefix, 
            IEnumerable<TableInfo> tables, 
            CodeDomProvider domProvider, 
            BLTConfig config,
            out string contextName)
		{
			var unit = new CodeCompileUnit();
			unit.ReferencedAssemblies.Add("System.dll");
			unit.ReferencedAssemblies.Add("BLToolkit.3.dll");
			var ns = new CodeNamespace(rootNamespace);
			ns.Imports.Add(new CodeNamespaceImport("System"));
			ns.Imports.Add(new CodeNamespaceImport("BLToolkit.DataAccess"));
			ns.Imports.Add(new CodeNamespaceImport("BLToolkit.Mapping"));
			unit.Namespaces.Add(ns);
			contextName = contextPrefix.CreateValidIdentifier(domProvider) + DB_CONTEXT_SUFFIX;
			var decl = new CodeTypeDeclaration(contextName);
			decl.IsClass = true;
			decl.IsPartial = true;
			ns.Types.Add(decl);
			decl.TypeAttributes = TypeAttributes.Public;
			decl.BaseTypes.Add(new CodeTypeReference(typeof (DbManager)));
		    GenerateCtors(decl, config.ConnectionInfo, contextName);
		    foreach (var table in tables)
			{
				var tableTypeName = table.TableName.CreateValidIdentifier(domProvider);
			    var tableFullNamespace = table.GetTableAbsoluteNamespace(rootNamespace, domProvider);
			    var tableFullTypeName = table.GetTableClassNameWithAbsoluteNamespace(rootNamespace, domProvider);
			    var prop = CreateProperty(decl, tableTypeName,
			                              new CodeTypeReference(typeof (Table<>).FullName,
			                                                    new CodeTypeReference(tableFullTypeName)),
			                              tableFullNamespace);
                prop.CustomAttributes.AddGeneratedCodeAttribute();
			}
			return unit;
		}

        private static void GenerateCtors(CodeTypeDeclaration declaration, BLTConnectionInfo connInfo,
            string contextName)
        {
            var baseType = typeof (DbManager);
            var ctors = baseType.GetConstructors();
            foreach (var ctorInfo in ctors)
            {
                var ctor = new CodeConstructor();
                if (ctorInfo.IsPublic)
                {
                    ctor.Attributes = MemberAttributes.Public;
                }
                var args = ctorInfo.GetParameters();
                if (args.Length == 0)
                {
                    //default ctor
                    if (connInfo.InjectConnStringIntoDefaultCtor)
                    {
                        var cctor = new CodeTypeConstructor();
                        var configStringName = contextName;
                        var connString = connInfo.FormatConnectionString();
                        //DbManager.AddConnectionString();
                        var addConnStrStatement = new CodeMethodInvokeExpression(
                            new CodeTypeReferenceExpression(baseType),
                            "AddConnectionString",
                            new CodePrimitiveExpression(configStringName),
                            new CodePrimitiveExpression(connString));
                        cctor.Statements.Add(addConnStrStatement);
                        cctor.CustomAttributes.AddGeneratedCodeAttribute();
                        declaration.Members.Add(cctor);
                        ctor.ChainedConstructorArgs.Add(new CodePrimitiveExpression(configStringName));
                    }
                }
                else
                {
                    foreach (var parameterInfo in args)
                    {
                        var arg = new CodeParameterDeclarationExpression(parameterInfo.ParameterType, parameterInfo.Name);
                        ctor.Parameters.Add(arg);
                        ctor.BaseConstructorArgs.Add(new CodeArgumentReferenceExpression(parameterInfo.Name));
                    }
                }
                ctor.CustomAttributes.AddGeneratedCodeAttribute();
                declaration.Members.Add(ctor);
            }
        }

		private static CodeMemberProperty CreateProperty(CodeTypeDeclaration typeDecl, string name, CodeTypeReference type, string rootNamespace)
		{
			var property = new CodeMemberProperty();
			property.Name = name;
			property.Type = type;
			property.Attributes = MemberAttributes.Public;
			property.GetStatements.Add(
				new CodeMethodReturnStatement(
					new CodeMethodInvokeExpression(
						new CodeMethodReferenceExpression(new CodeThisReferenceExpression(),
						                                  "GetTable",
						                                  new CodeTypeReference(string.Format("{0}.{1}", rootNamespace, name))
							)
						)
					)
				);
			typeDecl.Members.Add(property);
			return property;
		}
	}
}
